﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Common;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Factories;
using FarseerPhysics;
using chemicall.data;

namespace chemicall
{
    class LevelManager : GameScreen
    {
        private static LevelManager instance;

        /*Variables de haut niveau */
       //private GameManager gameManager;
        private Camera2D cam;
        private World world;

        /*Var dynamiques, liant joueur et Level*/
        private String labelUserName;
        //private int score;
        private int cptElek;
        //atomes libérés par le joueur (pour les 'pouvoirs') : info à chercher via le GameManager
        private bool[] freeAtomes = new bool[CONSTANTES.GAMEMANAGER_NOMBRE_NIVEAUX];

        /*Var propres à un Level, correspondant au level design*/
        private String levelName;
        private int levelNum, nbIons;
        private Character player;
        private List<Sprite> listDecor;
        private List<StaticEntity> listSta;
        private List<dynamicEntity> listDyn;
        private List<Agent> listAg;

        /*Interface*/
        private String state;
        private Color chargeText;
        private int alpha;
        private Boolean fade;
        // Sprite chargeBar;
        private Sprite chargeCurs;
        private String labelIonedLink;
        //infoBulle
        public InfoBulle infoBulle;
        private bool drawInfoBulle;
        private float time;

        public static LevelManager getInstance()
        {
            if (instance == null)
                instance = new LevelManager();
            return instance;
        }

        private LevelManager()
        {
           // this.gameManager = GameManager.getInstance();
            nbIons = 0;
            listDecor = new List<Sprite>();
            listSta = new List<StaticEntity>();
            listDyn = new List<dynamicEntity>();
            listAg = new List<Agent>();
            TransitionOnTime = TimeSpan.FromSeconds(0.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
            cam = new Camera2D();
            cam.Pos = new Vector2(500.0f, 200.0f);
            chargeText = new Color(0.0f, 0.0f, 0.0f);
            state = "Neutre";
            alpha = 100;
            fade = true;
            player = new Character(/*new Point(),*/ SoundManager.getInstance());
            myPause = new SimulatePause();
            infoBulle = InfoBulle.getInstance();
            drawInfoBulle = false;
            time = 0.0f;
        }

        /// <summary>
        /// Reset toutes les infos relatives au LevelManager.
        /// Fonction à appeler lorsque l'on charge un nouveau niveau
        /// </summary>
        private void resetLevelManager()
        {
            //this.player = null;
            nbIons = 0;
            listDecor = new List<Sprite>();
            listSta = new List<StaticEntity>();
            listDyn = new List<dynamicEntity>();
            listAg = new List<Agent>();
            TransitionOnTime = TimeSpan.FromSeconds(0.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
            cam = new Camera2D();
            cam.Pos = new Vector2(500.0f, 200.0f);
            chargeText = new Color(0.0f, 0.0f, 0.0f);
            state = "Neutre";
            alpha = 100;
            fade = true; 
            infoBulle = InfoBulle.getInstance();
            drawInfoBulle = false;
            time = 0.0f;
        }

        /// <summary>
        /// Permet de charger et de convertir les données issues du fichier XML de définition d'un 
        /// niveau.
        /// </summary>
        private void LoadXMLLevelDescription(String XMLLevelFile)
        {            
            LevelDescription level = ScreenManager.CurrentScreen.content.Load<LevelDescription>(XMLLevelFile);
            this.levelNum = level.num;
            this.levelName = level.name;
            //Point screenCenter = ScreenManager.GraphicsDevice.Viewport.Bounds.Center;

            foreach(EntityDescription ent in level.listEntities)
            {
                Vector2 init = new Vector2(ent.initPosXY.X, ent.initPosXY.Y);
                Rectangle rect = new Rectangle(0, 0, ent.dimXY.X, ent.dimXY.Y);
                switch (ent.typeID)
                {
                    case 0: //Background
                        Sprite mBackgroundOne = new Sprite();
                        mBackgroundOne.LoadContent(content, ent.name, rect, null);
                        mBackgroundOne.Position = init;
                        listDecor.Add(mBackgroundOne);
                        break;

                    case 1: //Avatar
                        player = new Character(/*screenCenter,*/ SoundManager.getInstance());
                        player.LoadContent(content, world, init, rect, rect);
                        cptElek = player.getNbElec();
                        break;

                    case 2: // Anion
                        Anion anion = new Anion(/*screenCenter,*/ 20, SoundManager.getInstance());
                        anion.LoadContent(content, world, init, rect, rect);
                        listAg.Add(anion);
                        this.nbIons++;
                        break;

                    case 3: // Cation
                        Cation cat = new Cation(/*screenCenter,*/ 20, SoundManager.getInstance());
                        cat.LoadContent(content, world, init, rect, rect);
                        listAg.Add(cat);
                        this.nbIons++;
                        break;
                    case 50: // Exit
                        StaticEntity exit  = new StaticEntity();
                        exit.LoadContent(content,world, ent.name,init, rect,null, exit);
                        listSta.Add(exit);
                        break;

                    default:
                        if (ent.typeID == 100) // Ground
                        {
                            StaticEntity ground = new StaticEntity();
                            ground.LoadContent(content, world, ent.name, init, rect, null, ground);
                            listSta.Add(ground);
                        }
                        else // à définir
                            Console.WriteLine("Autre");
                        break;
                }
            }
            
        }

        public void LoadLevel(int numLevel)
        {
            SoundManager.getInstance().stopEvent(CONSTANTES.MENU_MUSIC_ID);
            SoundManager.getInstance().startEvent(CONSTANTES.INGAME_MUSIC_ID);
            SoundManager.getInstance().setParameterValue(CONSTANTES.INGAME_MUSIC_ID, CONSTANTES.CHARGE_PARAMETER, (float)0.6f);
            this.resetLevelManager();
            int totalLevel = 2; //TODO lire et référencer les fichiers de Level XML
            String fileName = "level";
            if (0 <= numLevel && numLevel <= totalLevel)
            {
                fileName += numLevel;
                this.LoadXMLLevelDescription(fileName);
            }
            else
                Console.WriteLine("Fichier inexistant ! Vérifiez le numéro du niveau souhaité");
        }

        public void EndLevel(/*int etat*/)
        {
            //Console.Write(" fin du level ");
            bool perfect =true;
            foreach (Agent ag in listAg)
            {
                if (!ag.stable)
                {
                    perfect = false;
                    break; // sortie prématurée de la boucle
                }
            }
            int linkedIons = 0;
            foreach (Agent ag in listAg)
                if (ag.stable)
                    linkedIons++;
            //etat == 0 <=> joueur possède 6 electron (stable en notre sens)
            GameManager.getInstance().SaveLevel(levelNum, player.etat == 0, linkedIons, perfect);
            GameManager.getInstance().SaveGameManager(GameManager.getInstance().fichier, GameManager.getInstance().nameUser);
            System.Threading.Thread.Sleep(400);
            if (perfect)
            {
                ScreenManager.AddScreen(new ScreenWin(levelNum), PlayerIndex.One);
            }
            else
            {
                this.resetLevelManager();
                ScreenManager.AddScreen(new ChooseLevelMenu(), PlayerIndex.One);
            }
            SoundManager.getInstance().stopEvent(CONSTANTES.INGAME_MUSIC_ID);
            SoundManager.getInstance().startEvent(CONSTANTES.MENU_MUSIC_ID);
        }

        public override void Activate(bool instancePreserved)
        {
            if (!instancePreserved)
            {
                if (content == null)
                    content = new ContentManager(ScreenManager.Game.Services, "Content");

                CreateWorld();
                //this.LoadXMLLevelDescription();

                chargeCurs = new Sprite();
                chargeCurs.LoadContent(content, "spriteSheetBarInterface", new Rectangle(0, 0, 256, 35), new Rectangle(0, 0, 256, 35));
                chargeCurs.Position = new Vector2(120, 20);

                labelUserName = GameManager.getInstance().nameUser;

                _classicFont = content.Load<SpriteFont>("classicPolice");
                // TODO: use this.Content to load your game content here
                this.screenState = ScreenState.Active;

                this.spriteBatch = ScreenManager.SpriteBatch;
            }
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            if (!myPause.Paused)
            {
                float timeStep = 1 / 25.0f;

                // Allows the game to exit
                /* if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                     this.Exit();*/

                // TODO: Add your update logic here
                foreach (dynamicEntity se in listDyn)
                    se.Update(gameTime);

                foreach (Agent ag in listAg)
                    ag.Update(gameTime);

                player.Update(gameTime, world);

                cptElek = player.getNbElec();

                //update the world.
                world.Step(Math.Min((float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.001f, timeStep));

                Vector2 aDirection = new Vector2(-1, 0);
                Vector2 aSpeed = new Vector2(160, 0);

                chargeCurs.Update(gameTime, Vector2.Zero, Vector2.Zero);

                cam._pos = player.Position;

                //infoBull
                time += (float)gameTime.ElapsedGameTime.TotalSeconds;
                if (time <= infoBulle.duree)
                {
                    drawInfoBulle = true;
                }
                else
                {
                    drawInfoBulle = false;
                    time = 0.0f;
                    infoBulle.setInfoBulle("", 0.0f);
                }

                updateInterface();
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Draw(GameTime gameTime)
        {
            this.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.AnisotropicWrap, DepthStencilState.DepthRead, RasterizerState.CullNone, null, cam.get_transformation(ScreenManager.GraphicsDevice));
            foreach (Sprite sp in listDecor)
                sp.Draw(this.spriteBatch);
            foreach (StaticEntity st in listSta)
                st.Draw(this.spriteBatch);
            foreach (dynamicEntity se in listDyn)
                se.Draw(this.spriteBatch);

            player.Draw(this.spriteBatch);

            foreach (Agent ag in listAg)
                ag.Draw(this.spriteBatch);
            spriteBatch.End();

            /*INTERFACE (non soumis aux mouvements de la camera)*/
            this.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.AnisotropicWrap, DepthStencilState.DepthRead, RasterizerState.CullNone);

            Rectangle field = ScreenManager.GraphicsDevice.Viewport.Bounds;

            //infoBulle
            if (String.Compare(infoBulle.info, "") != 0 && drawInfoBulle)
            {
                Texture2D cadre = content.Load<Texture2D>("cadre");
                spriteBatch.Draw(cadre, new Rectangle((field.Width / 2) - (infoBulle.info.Length * 4) - 10, 385, infoBulle.info.Length * 11, 75), Color.Black);
                spriteBatch.DrawString(this._classicFont, "Aide :\n" + infoBulle.info, new Vector2((field.Width / 2) - (infoBulle.info.Length * 4), 400), Color.White, 0, Vector2.Zero, 0.7f, SpriteEffects.None, 0);
            }

            /*Top Left*/
            chargeCurs.Draw(this.spriteBatch);
            spriteBatch.DrawString(this._classicFont, state, new Vector2(20,30), chargeText, 0, Vector2.Zero, 1.0f, SpriteEffects.None, 0);
            // Enlever pour le moment à voir si on le garde ou pas
           // spriteBatch.DrawString(this._classicFont, Convert.ToString(cptElek), new Vector2(120, 70), chargeText, 0, Vector2.Zero, 1.0f, SpriteEffects.None, 0);
           // spriteBatch.DrawString(this._classicFont, "8", new Vector2(120, 0), chargeText, 0, Vector2.Zero, 1.0f, SpriteEffects.None, 0);

            /*Top Right*/
            spriteBatch.DrawString(this._classicFont, "Niveau : " + levelName, new Vector2(field.Width-120, 20), Color.Black, 0, Vector2.Zero, 0.5f, SpriteEffects.None, 0);

            /*Bottom Left*/
            int linkedIons = 0;
            foreach (Agent ag in listAg)
                if (ag.stable)
                    linkedIons++;
            labelIonedLink = "Liaisons Ioniques : " + linkedIons + '/' + nbIons;
            spriteBatch.DrawString(this._classicFont, labelIonedLink, new Vector2(20, field.Height - 30), Color.Black, 0, Vector2.Zero, 0.5f, SpriteEffects.None, 0);
            //chargeBar.Draw(this.spriteBatch);

            
            spriteBatch.End();

            base.Draw(gameTime);
        }

        /*Question : mettre l'info dans les paramètre des Levels? Comme ça on peut faire des
         niveaux avec plus ou moins de gravité*/
        void CreateWorld()
        {
            // Define the gravity vector.
            Vector2 gravity = new Vector2(0.0f, 300.0f);

            // Construct a world object, which will hold and simulate the rigid bodies.
            world = new World(gravity);
            world.EnableSubStepping = true;
        }

        private void updateInterface()
        {
            if (cptElek == 6)
            {
                chargeText = Color.Green;
                state = "Neutre";
                chargeCurs.currAnim = cptElek-1;
            }
            else if (cptElek > 6)
            {
                chargeText = Color.Red;
                state = "Exces";
                chargeCurs.currAnim = cptElek-1;

            }
            else
            {
                chargeText = Color.Blue;
                state = "Default";
                chargeCurs.currAnim = cptElek-1;
            }
            
            if (alpha < 200 && fade)
                alpha += 5;
            else if (alpha >= 200 && fade)
                fade = false;
            else if (alpha > 100 && !fade)
                alpha -= 5;
            else if (alpha <= 100 && !fade)
                fade = true;
             

            chargeText.A = (byte)(alpha);


        }

        public int getNumLevel()
        {
            return this.levelNum;
        }
    }
}
